Add `--name` and `--example` to cargo run
authorTomas Sedovic <tomas@sedovic.cz>
Mon, 27 Oct 2014 21:49:20 +0000 (22:49 +0100)
committerTomas Sedovic <tomas@sedovic.cz>
Tue, 28 Oct 2014 23:20:44 +0000 (00:20 +0100)
This lets us compile and run examples using `cargo run --example NAME`.
Selecting the other binary targets is now done using the `--name` flag.

`cargo run` falls back to the old behaviour (running the only bin target
in the project, failing if there are more) in neither `--name` nor
`--example` are present.

Closes #538

src/bin/run.rs
src/cargo/ops/cargo_run.rs
tests/test_cargo_run.rs

index 3257e110f940991344c9da1a830b013ae917e7c7..efdf78fd299b28a72d834b0c10626eb69be79531 100644 (file)
@@ -2,11 +2,14 @@ use std::io::process::ExitStatus;
 
 use cargo::ops;
 use cargo::core::{MultiShell};
-use cargo::util::{CliResult, CliError};
+use cargo::core::manifest::{BinTarget, ExampleTarget};
+use cargo::util::{CliResult, CliError, human};
 use cargo::util::important_paths::{find_root_manifest_for_cwd};
 
 #[deriving(Decodable)]
 struct Options {
+    flag_name: Option<String>,
+    flag_example: Option<String>,
     flag_jobs: Option<uint>,
     flag_features: Vec<String>,
     flag_no_default_features: bool,
@@ -25,6 +28,8 @@ Usage:
 
 Options:
     -h, --help              Print this message
+    --name NAME             Name of the bin target to run
+    --example NAME          Name of the example target to run
     -j N, --jobs N          The number of jobs to run in parallel
     --release               Build artifacts in release mode, with optimizations
     --features FEATURES     Space-separated list of features to also build
@@ -33,6 +38,11 @@ Options:
     --manifest-path PATH    Path to the manifest to execute
     -v, --verbose           Use verbose output
 
+If neither `--name` or `--example` are given, then if the project only has one
+bin target it will be run. Otherwise `--name` specifies the bin target to run,
+and `--example` specifies the example target to run. At most one of `--name` or
+`--example` can be provided.
+
 All of the trailing arguments are passed as to the binary to run.
 ";
 
@@ -40,8 +50,16 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>
     shell.set_verbose(options.flag_verbose);
     let root = try!(find_root_manifest_for_cwd(options.flag_manifest_path));
 
+    let env = if options.flag_example.is_some() {
+        "test"
+    } else if options.flag_release {
+        "release"
+    } else {
+        "compile"
+    };
+
     let mut compile_opts = ops::CompileOptions {
-        env: if options.flag_release { "release" } else { "compile" },
+        env: env,
         shell: shell,
         jobs: options.flag_jobs,
         target: options.flag_target.as_ref().map(|t| t.as_slice()),
@@ -51,7 +69,18 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>
         spec: None,
     };
 
-    let err = try!(ops::run(&root, &mut compile_opts,
+    let (target_kind, name) = match (options.flag_name, options.flag_example) {
+        (Some(bin), None) => (BinTarget, Some(bin)),
+        (None, Some(example)) => (ExampleTarget, Some(example)),
+        (None, None) => (BinTarget, None),
+        (Some(_), Some(_)) => return Err(CliError::from_boxed(
+            human("specify either `--name` or `--example`, not both"), 1)),
+    };
+
+    let err = try!(ops::run(&root,
+                            target_kind,
+                            name,
+                            &mut compile_opts,
                             options.arg_args.as_slice()).map_err(|err| {
         CliError::from_boxed(err, 101)
     }));
@@ -65,4 +94,3 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>
         }
     }
 }
-
index 16fa41b1aafedb8730de37455bc116994d41ee83..bd1531491139b5a06b52844e30ccf2af4ee18edd 100644 (file)
@@ -2,10 +2,13 @@ use std::os;
 
 use ops;
 use util::{CargoResult, human, process, ProcessError, Require};
+use core::manifest::{TargetKind, LibTarget, BinTarget, ExampleTarget};
 use core::source::Source;
 use sources::PathSource;
 
 pub fn run(manifest_path: &Path,
+           target_kind: TargetKind,
+           name: Option<String>,
            options: &mut ops::CompileOptions,
            args: &[String]) -> CargoResult<Option<ProcessError>> {
     let mut src = try!(PathSource::for_path(&manifest_path.dir_path()));
@@ -13,14 +16,21 @@ pub fn run(manifest_path: &Path,
     let root = try!(src.get_root_package());
     let env = options.env;
     let mut bins = root.get_manifest().get_targets().iter().filter(|a| {
-        a.is_bin() && a.get_profile().get_env() == env
+        let matches_kind = match target_kind {
+            BinTarget => a.is_bin(),
+            ExampleTarget => a.is_example(),
+            LibTarget(_) => false,
+        };
+        let matches_name = name.as_ref().map_or(true, |n| n.as_slice() == a.get_name());
+        matches_kind && matches_name && a.get_profile().get_env() == env
     });
     let bin = try!(bins.next().require(|| {
         human("a bin target must be available for `cargo run`")
     }));
     match bins.next() {
-        Some(..) => return Err(human("`cargo run` requires that a project only \
-                                      have one executable")),
+        Some(..) => return Err(
+            human("`cargo run` requires that a project only have one executable. \
+                   Use the `--name` option to specify which one to run")),
         None => {}
     }
 
@@ -28,7 +38,7 @@ pub fn run(manifest_path: &Path,
     let dst = manifest_path.dir_path().join("target");
     let dst = match options.target {
         Some(target) => dst.join(target),
-        None => dst,
+        None => if bin.is_example() { dst.join("examples") } else { dst },
     };
     let exe = match bin.get_profile().get_dest() {
         Some(s) => dst.join(s).join(bin.get_name()),
index f05aac810f687527de9272c688d3dff5b55ed2b1..97fc5e31cdba10719eec56d3ba3bd119204c9f4e 100644 (file)
@@ -1,6 +1,6 @@
 use std::path;
 
-use support::{project, execs, path2url};
+use support::{project, cargo_dir, execs, path2url};
 use support::{COMPILING, RUNNING};
 use hamcrest::{assert_that, existing_file};
 
@@ -98,7 +98,126 @@ test!(too_many_bins {
     assert_that(p.cargo_process("run"),
                 execs().with_status(101)
                        .with_stderr("`cargo run` requires that a project only \
-                                     have one executable\n"));
+                                     have one executable. Use the `--name` option \
+                                     to specify which one to run\n"));
+})
+
+test!(specify_name {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+        "#)
+        .file("src/lib.rs", "")
+        .file("src/bin/a.rs", r#"
+            extern crate foo;
+            fn main() { println!("hello a.rs"); }
+        "#)
+        .file("src/bin/b.rs", r#"
+            extern crate foo;
+            fn main() { println!("hello b.rs"); }
+        "#);
+
+    assert_that(p.cargo_process("run").arg("--name").arg("a"),
+                execs().with_status(0).with_stdout(format!("\
+{compiling} foo v0.0.1 ({dir})
+{running} `target{sep}a`
+hello a.rs
+",
+        compiling = COMPILING,
+        running = RUNNING,
+        dir = path2url(p.root()),
+        sep = path::SEP).as_slice()));
+
+    assert_that(p.process(cargo_dir().join("cargo")).arg("run").arg("--name").arg("b"),
+                execs().with_status(0).with_stdout(format!("\
+{running} `target{sep}b`
+hello b.rs
+",
+        running = RUNNING,
+        sep = path::SEP).as_slice()));
+})
+
+test!(run_example {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+        "#)
+        .file("src/lib.rs", "")
+        .file("examples/a.rs", r#"
+            fn main() { println!("example"); }
+        "#)
+        .file("src/bin/a.rs", r#"
+            fn main() { println!("bin"); }
+        "#);
+
+    assert_that(p.cargo_process("run").arg("--example").arg("a"),
+                execs().with_status(0).with_stdout(format!("\
+{compiling} foo v0.0.1 ({dir})
+{running} `target{sep}examples{sep}a`
+example
+",
+        compiling = COMPILING,
+        running = RUNNING,
+        dir = path2url(p.root()),
+        sep = path::SEP).as_slice()));
+})
+
+test!(either_name_or_example {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+        "#)
+        .file("src/bin/a.rs", r#"
+            fn main() { println!("hello a.rs"); }
+        "#)
+        .file("examples/b.rs", r#"
+            fn main() { println!("hello b.rs"); }
+        "#);
+
+    assert_that(p.cargo_process("run").arg("--name").arg("a").arg("--example").arg("b"),
+                execs().with_status(1)
+                       .with_stderr("specify either `--name` or `--example`, \
+                                     not both"));
+})
+
+test!(one_bin_multiple_examples {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+        "#)
+        .file("src/lib.rs", "")
+        .file("src/bin/main.rs", r#"
+            fn main() { println!("hello main.rs"); }
+        "#)
+        .file("examples/a.rs", r#"
+            fn main() { println!("hello a.rs"); }
+        "#)
+        .file("examples/b.rs", r#"
+            fn main() { println!("hello b.rs"); }
+        "#);
+
+    assert_that(p.cargo_process("run"),
+                execs().with_status(0).with_stdout(format!("\
+{compiling} foo v0.0.1 ({dir})
+{running} `target{sep}main`
+hello main.rs
+",
+        compiling = COMPILING,
+        running = RUNNING,
+        dir = path2url(p.root()),
+        sep = path::SEP).as_slice()));
 })
 
 test!(run_dylib_dep {